package com.yongxing.plugintwo;

import android.app.LoadedApk;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
import android.content.res.CompatibilityInfo;
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

import java.io.File;
import java.lang.ref.WeakReference;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import dalvik.system.DexClassLoader;

/**
 * Created by jiangyongxing on 2019/6/24.
 * 描述：
 */
public class HookUtil {

    private Object mIActivityManager;
    private Context mContext;
    private Handler mHHandler;
    public String pluginPath;
    private PackageParser.Package mAPackage;
    protected Map<ComponentName, ActivityInfo> mActivityInfos;

    {
        pluginPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/plugin-debug.apk";
    }

    /**
     * 将插件中的Activity伪装成一个已经注册的Activity
     *
     * @param context
     */
    public void hookStartActivity(Context context) {
        this.mContext = context;
        try {
            Class<?> activityManager = Class.forName("android.app.ActivityManager");
            // 获取到IActivityManagerSingleton属性
            Field activityManagerSingleton = activityManager.getDeclaredField("IActivityManagerSingleton");
            activityManagerSingleton.setAccessible(true);
            // 获取到IActivityManagerSingleton属性的值
            Object defaultValue = activityManagerSingleton.get(null);

            Class<?> singleton = Class.forName("android.util.Singleton");
            Field mInstance = singleton.getDeclaredField("mInstance");
            mInstance.setAccessible(true);
            mIActivityManager = mInstance.get(defaultValue);
            Class<?> iActivityManagerClass = Class.forName("android.app.IActivityManager");

            Object proxyInstance = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{iActivityManagerClass}, new StartActivityInvocationHandler());
            // 将系统中的iActivityManager换成我们的
            mInstance.set(defaultValue, proxyInstance);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 将被伪装的Activity给解析出来，解析插件中的Activity
     */
    public void hookLaunchActivity() {
        try {
            Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
            Field currentActivityThread = activityThreadClass.getDeclaredField("sCurrentActivityThread");
            currentActivityThread.setAccessible(true);
            Object activityThread = currentActivityThread.get(null);
            Field mH = activityThreadClass.getDeclaredField("mH");
            mH.setAccessible(true);
            mHHandler = (Handler) mH.get(activityThread);

            Field mCallback = Handler.class.getDeclaredField("mCallback");
            mCallback.setAccessible(true);
            mCallback.set(mHHandler, new ActivitymH());


        } catch (Exception e) {
            e.printStackTrace();
        }

    }


    class StartActivityInvocationHandler implements InvocationHandler {

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (mIActivityManager != null) {
                Intent intent = null;
                int index = 0;
                if ("startActivity".equals(method.getName())) {
                    Log.e("jiang", "invoke:" + method.getName());
                    for (int i = 0; i < args.length; i++) {
                        Object arg = args[i];
                        if (arg instanceof Intent) {
                            intent = (Intent) arg;
                            index = i;
                            break;
                        }
                    }

                    ComponentName originalComponent = intent.getComponent();
                    Intent newIntent = new Intent();
                    ComponentName componentName = new ComponentName(mContext, ProxyHookActivity.class);
                    newIntent.setComponent(componentName);
                    newIntent.putExtra("oldIntent", intent);
                    newIntent.putExtra("oldPackage", mActivityInfos.containsKey(originalComponent) ? mActivityInfos.get(originalComponent).packageName : mContext.getPackageName());
                    newIntent.putExtra("is_plugin", true);
                    args[index] = newIntent;

                }
            }
            return method.invoke(mIActivityManager, args);
        }
    }

    class ActivitymH implements Handler.Callback {
        @Override
        public boolean handleMessage(Message msg) {
            if (mHHandler != null) {
                if (msg.what == 159) {
                    // 表示launchActivity
                    handleLaunchActivity(msg);
                }
                mHHandler.handleMessage(msg);
            }
            return true;
        }
    }

    private void handleLaunchActivity(Message msg) {
        String packageName = mContext.getPackageName();
        Object obj = msg.obj;
        try {
            Field intentField = obj.getClass().getDeclaredField("mActivityCallbacks");
            intentField.setAccessible(true);
            ArrayList mActivityCallbacks = (ArrayList) intentField.get(obj);
            Object launchActivityItem = mActivityCallbacks.get(0);


            Field mIntent = launchActivityItem.getClass().getDeclaredField("mIntent");
            mIntent.setAccessible(true);
            Intent newIntent = (Intent) mIntent.get(launchActivityItem);
            Intent realIntent = newIntent.getParcelableExtra("oldIntent");
            newIntent.setComponent(realIntent.getComponent());

            boolean isPlugin = newIntent.getBooleanExtra("is_plugin", false);


            // 设置ActivityInfo中的主题
            Field activityInfoField = launchActivityItem.getClass().getDeclaredField("mInfo");
            activityInfoField.setAccessible(true);
            ActivityInfo activityInfo = (ActivityInfo) activityInfoField.get(launchActivityItem);


            if (isPlugin) {
                String oldPackage = newIntent.getStringExtra("oldPackage");
                activityInfo.applicationInfo.packageName = oldPackage;
                packageName = oldPackage;
            }
            activityInfo.theme = getTheme(newIntent, packageName);
            activityInfoField.set(launchActivityItem, activityInfo);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public PackageParser.Package getPackageInfo(Context context) {
        if (mAPackage == null) {
            mAPackage = PackageParserCompat.parsePackage(context, new File(pluginPath), PackageParser.PARSE_MUST_BE_APK);

            // Cache activities
            Map<ComponentName, ActivityInfo> activityInfos = new HashMap<ComponentName, ActivityInfo>();
            for (PackageParser.Activity activity : mAPackage.activities) {
                activity.info.metaData = activity.metaData;
                activityInfos.put(activity.getComponentName(), activity.info);
            }
            this.mActivityInfos = Collections.unmodifiableMap(activityInfos);
        }

        return mAPackage;
    }


    public void injectPluginClass(Context context) {
        String cachePath = context.getCacheDir().getAbsolutePath();
        DexClassLoader dexClassLoader = new DexClassLoader(pluginPath, cachePath, cachePath, context.getClassLoader());
        try {
            Class<?> baseDexClassLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader");
            Field pathListFiled = baseDexClassLoaderClass.getDeclaredField("pathList");
            pathListFiled.setAccessible(true);
            // 获取到了插件中的BaseDexClassLoader的pathList属性
            Object mPathListObject = pathListFiled.get(dexClassLoader);

            Class<?> pathListClass = mPathListObject.getClass();
            Field dexElementsField = pathListClass.getDeclaredField("dexElements");
            dexElementsField.setAccessible(true);
            // 获取到了插件中的dexElements数组
            Object dexElementsObject = dexElementsField.get(mPathListObject);

            // 可以通过上下文获取到宿主当前的ClassLoader对象
            ClassLoader classLoader = context.getClassLoader();
            Class<?> appBaseDexClassLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader");
            Field appPathListField = appBaseDexClassLoaderClass.getDeclaredField("pathList");
            appPathListField.setAccessible(true);
            Object appPathListObject = appPathListField.get(classLoader);

            Class<?> appPathListCLass = appPathListObject.getClass();
            Field appDexElementsFiled = appPathListCLass.getDeclaredField("dexElements");
            appDexElementsFiled.setAccessible(true);

            // 找到宿主的dexElements数组
            Object appElementsObject = appDexElementsFiled.get(appPathListObject);

            // 将宿主数组跟插件数组的长度相加，合并成一个新的数组，然后通过反射重新注入到系统中
            int dexElementsLength = Array.getLength(dexElementsObject);
            int appElementsLength = Array.getLength(appElementsObject);
            // 新数组的长度
            int newElementsLength = dexElementsLength + appElementsLength;

            // 找到Element数组的元素类型
            Class<?> componentType = dexElementsObject.getClass().getComponentType();
            Object newElementsArray = Array.newInstance(componentType, newElementsLength);

            for (int i = 0; i < newElementsLength; i++) {
                // 先融合 插件的Elements
                if (i < dexElementsLength) {
                    Array.set(newElementsArray, i, Array.get(dexElementsObject, i));
                } else {
                    Array.set(newElementsArray, i, Array.get(appElementsObject, i - dexElementsLength));
                }
            }
            appDexElementsFiled.set(appPathListObject, newElementsArray);
        } catch (Exception e) {
            Log.e("jiang", "合并dex的时候出现崩溃了" + e.toString());
            e.printStackTrace();
        }
    }

    public void putLoadedAPk() {
        try {
            Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
            // 先还原activityThread对象
            Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
            currentActivityThreadMethod.setAccessible(true);
            Object currentActivityThread = currentActivityThreadMethod.invoke(null);

            // 再来还原  mPackages  对象
            // 获取到 mPackages 这个静态成员变量, 这里缓存了apk包的信息
            Field mPackagesField = activityThreadClass.getDeclaredField("mPackages");
            mPackagesField.setAccessible(true);
            Map<String, WeakReference<LoadedApk>> mPackages = (Map) mPackagesField.get(currentActivityThread);
            for (Map.Entry<String, WeakReference<LoadedApk>> stringWeakReferenceEntry : mPackages.entrySet()) {
                Log.e("jiang", "putLoadedAPk:" + stringWeakReferenceEntry.getKey() + stringWeakReferenceEntry.getValue().get().getPackageName());
            }
            // 找到  getPackageInfoNoCheck   method 方法
            Method getPackageInfoNoCheckMethod = activityThreadClass.getDeclaredMethod(
                    "getPackageInfoNoCheck", ApplicationInfo.class, CompatibilityInfo.class);

            // 得到 CompatibilityInfo  里面的  静态成员变量       DEFAULT_COMPATIBILITY_INFO  类型  CompatibilityInfo
            Field defaultCompatibilityInfoField = CompatibilityInfo.class.getDeclaredField("DEFAULT_COMPATIBILITY_INFO");
            CompatibilityInfo defaultCompatibilityInfo = (CompatibilityInfo) defaultCompatibilityInfoField.get(null);

            ApplicationInfo applicationInfo = parseReceivers(pluginPath);

            // 一个问题  传参 ApplicationInfo ai 一定是与插件相关    ApplicationInfo----》插件apk文件
            // LoadedApk getPackageInfoNoCheck(ApplicationInfo ai, CompatibilityInfo compatInfo)
            Object loadedApk = getPackageInfoNoCheckMethod.invoke(currentActivityThread, applicationInfo, defaultCompatibilityInfo);


            String odexPath = mContext.getCacheDir().getAbsolutePath();
            String libDir = mContext.getCacheDir().getAbsolutePath();

            ClassLoader classLoader = new CustomClassLoader(pluginPath, odexPath, libDir, mContext.getClassLoader());
            Field mClassLoaderField = loadedApk.getClass().getDeclaredField("mClassLoader");
            mClassLoaderField.setAccessible(true);
            mClassLoaderField.set(loadedApk, classLoader);
            WeakReference weakReference = new WeakReference(loadedApk);

            // 最终目的  是要替换ClassLoader  不是替换LoaderApk
            mPackages.put(applicationInfo.packageName, weakReference);
        } catch (Exception e) {

        }
    }

    private ApplicationInfo parseReceivers(String path) {
        // Package对象
        // PackageParser pp = new PackageParser();

        // PackageParser.Package  pkg = pp.parsePackage(scanFile, parseFlags);

        try {
            Class packageParserClass = Class.forName("android.content.pm.PackageParser");
            Method parsePackageMethod = packageParserClass.getDeclaredMethod("parsePackage", File.class, int.class);
            Object packageParser = packageParserClass.newInstance();
            Object packageObj = parsePackageMethod.invoke(packageParser, new File(path), PackageManager.GET_ACTIVITIES);

//            generateActivityInfo方法
            Class<?> packageUserStateClass = Class.forName("android.content.pm.PackageUserState");
            Object defaultUserState = packageUserStateClass.newInstance();
            // 目的     generateApplicationInfo  方法  生成  ApplicationInfo
            // 需要调用 android.content.pm.PackageParser#generateActivityInfo(android.content.pm.ActivityInfo, int, android.content.pm.PackageUserState, int)
            //      generateApplicationInfo
            Method generateApplicationInfoMethod = packageParserClass.getDeclaredMethod("generateApplicationInfo",
                    packageObj.getClass(),
                    int.class,
                    packageUserStateClass);
            ApplicationInfo applicationInfo = (ApplicationInfo) generateApplicationInfoMethod.invoke(packageParser, packageObj, 0, defaultUserState);

            applicationInfo.sourceDir = path;
            applicationInfo.publicSourceDir = path;
            return applicationInfo;
            //generateActivityInfo
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;

    }

    public void onDestroy() {
        mIActivityManager = null;
        mHHandler = null;
    }

    /**
     * 以下代码是为了获取插件Activity中的主题
     *
     * @param intent
     * @return
     */
    public int getTheme(Intent intent, String packageName) {
        return getTheme(getComponent(intent, packageName));
    }

    public static ComponentName getComponent(Intent intent, String packageName) {
        if (intent == null) {
            return null;
        }
        return new ComponentName(packageName,
                intent.getComponent().getClassName());

    }

    public int getTheme(ComponentName component) {
        ActivityInfo activityInfo = mActivityInfos.get(component);
        if (activityInfo == null) {
            return 0;
        }
        if (activityInfo.theme != 0) {
            return activityInfo.theme;
        }
        ApplicationInfo appInfo = activityInfo.applicationInfo;
        if (null != appInfo && appInfo.theme != 0) {
            return appInfo.theme;
        }
        return selectDefaultTheme(0, Build.VERSION.SDK_INT);
    }

    public static int selectDefaultTheme(final int curTheme, final int targetSdkVersion) {
        return selectSystemTheme(curTheme, targetSdkVersion,
                0, 0, 0, 0);
    }

    public static int selectSystemTheme(final int curTheme, final int targetSdkVersion, final int orig, final int holo, final int dark, final int deviceDefault) {
        if (curTheme != 0) {
            return curTheme;
        }

        if (targetSdkVersion < 11 /* Build.VERSION_CODES.HONEYCOMB */) {
            return orig;
        }

        if (targetSdkVersion < 14 /* Build.VERSION_CODES.ICE_CREAM_SANDWICH */) {
            return holo;
        }

        if (targetSdkVersion < 24 /* Build.VERSION_CODES.N */) {
            return dark;
        }

        return deviceDefault;
    }
}
